package com.parent.utils;

import cn.hutool.core.io.IoUtil;
import cn.hutool.poi.excel.BigExcelWriter;
import cn.hutool.poi.excel.ExcelReader;
import cn.hutool.poi.excel.ExcelUtil;
import cn.hutool.poi.excel.ExcelWriter;
import com.parent.excel.ExcelField;
import com.parent.excel.ExcelFieldMap;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.poi.ss.formula.functions.T;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 通用功能未测试
 *
 * @author yan
 * @date 2023/4/17 0017 11:55
 */
public class ExcelUtils {


    /**
     * read
     */
    public static Map<String, String> getHeaderAliasForRead(Class<?> tClass) {
        Field[] declaredFields = tClass.getDeclaredFields();
        Map<String, String> stringMap = new LinkedHashMap<>();
        for (Field declaredField : declaredFields) {
            declaredField.setAccessible(true);
            ExcelField annotation = declaredField.getAnnotation(ExcelField.class);
            if (annotation != null) {
                String[] values = annotation.values();
                for (String name : values) {
                    stringMap.put(name, declaredField.getName());
                }
            }
        }
        return stringMap;
    }

    /**
     * out
     */

    /**
     * 扫码ExcelField注解生成excl标题信息
     *
     * @param tClass
     * @return
     */
    public static Map<String, String> getHeaderAliasForOut(Class<?> tClass) {
        Field[] declaredFields = tClass.getDeclaredFields();
        Map<String, String> stringMap = new LinkedHashMap<>();
        for (Field declaredField : declaredFields) {
            declaredField.setAccessible(true);
            ExcelField annotation = declaredField.getAnnotation(ExcelField.class);
            if (annotation != null) {
                String[] values = annotation.values();
                for (String name : values) {
                    stringMap.put(declaredField.getName(), name);
                }
            }
        }
        return stringMap;
    }


    /**
     * 获取一个类里所有的@ExcelField 的映射关系
     *
     * @param clazz
     * @return
     */
    public static List<ExcelFieldMap> getAllFieldMaps(Class<?> clazz) {
        //Class<Demo> clazz = Demo.class; // 获取Demo类的Class对象
        Field[] fields = clazz.getDeclaredFields(); // 获取Demo类中所有定义的字段
        List<ExcelFieldMap> excelFieldMaps = new ArrayList<>(fields.length);
        for (Field field : fields) {
            Annotation annotation = field.getAnnotation(ExcelField.class); // 获取当前字段上的ExcelField注解
            if (annotation instanceof ExcelField) { // 判断当前字段是否有ExcelField注解
                ExcelField excelField = (ExcelField) annotation; // 将注解强制转换为ExcelField类型
                //System.out.println(field.getName() + ": " + excelField.name()); // 输出字段名和对应的Excel表格列名
                String[] values = excelField.values();
                for (String name : values) {
                    ExcelFieldMap excelFieldMap = new ExcelFieldMap(name, field.getName());
                    excelFieldMaps.add(excelFieldMap);
                }
            }
        }
        return excelFieldMaps;
    }

    /**
     * 获取一个类里所有必传字段的@ExcelField 的映射关系
     *
     * @param tClass
     * @return
     */
    public static List<ExcelFieldMap> getAllMustFieldMaps(Class<?> tClass) {
        //Class<Demo> tClass = Demo.class; // 获取Demo类的Class对象
        Field[] fields = tClass.getDeclaredFields(); // 获取Demo类中所有定义的字段
        List<ExcelFieldMap> excelFieldMaps = new ArrayList<>(fields.length);
        for (Field field : fields) {
            Annotation annotation = field.getAnnotation(ExcelField.class); // 获取当前字段上的ExcelField注解
            if (annotation instanceof ExcelField) { // 判断当前字段是否有ExcelField注解
                ExcelField excelField = (ExcelField) annotation; // 将注解强制转换为ExcelField类型
                //System.out.println(field.getName() + ": " + excelField.name()); // 输出字段名和对应的Excel表格列名
                if (excelField.bool()) {

                    String[] values = excelField.values();
                    for (String name : values) {
                        ExcelFieldMap excelFieldMap = new ExcelFieldMap(name, field.getName());
                        excelFieldMaps.add(excelFieldMap);
                    }
                }
            }
        }
        return excelFieldMaps;
    }

    /**
     * 扫码ExcelField注解生成excl标题信息
     *
     * @param tClass
     * @return
     */
    public static Map<String, String> getHeaderAlias(Class<?> tClass) {
        Field[] declaredFields = tClass.getDeclaredFields();
        Map<String, String> stringMap = new LinkedHashMap<>();
        for (Field declaredField : declaredFields) {
            declaredField.setAccessible(true);
            ExcelField annotation = declaredField.getAnnotation(ExcelField.class);
            if (annotation != null) {
                String[] values = annotation.values();
                for (String name : values) {
                    stringMap.put(declaredField.getName(), name);
                }
            }
        }
        return stringMap;
    }

    /**
     * 获取动态字段映射关系
     *
     * @param maps
     * @return
     */
    private static Map<String, String> getHeaderAliasDynamicFieldMap(List<ExcelFieldMap> maps) {
        Map<String, String> stringMap = new LinkedHashMap<>();
        maps.stream().forEach(v -> {
            String name = v.getName();
            String fieldName = v.getFieldName();
            stringMap.put(fieldName, name);
        });
        return stringMap;
    }

    /**
     * 扫码ExcelField注解生成excl标题信息 排除excludes-》@ExcelField(name = "描述") name
     *
     * @param tClass
     * @return
     */
    public static Map<String, String> getHeaderAliasExcludes(Class<?> tClass, List<String> excludes) {
        Field[] declaredFields = tClass.getDeclaredFields();
        Map<String, String> stringMap = new LinkedHashMap<>();
        for (Field declaredField : declaredFields) {
            declaredField.setAccessible(true);
            ExcelField annotation = declaredField.getAnnotation(ExcelField.class);
            if (annotation != null) {
                String[] values = annotation.values();
                for (String name : values) {
                    if (!excludes.contains(name)) {
                        stringMap.put(declaredField.getName(), name);
                    }
                }
            }
        }
        return stringMap;
    }

    /**
     * 扫码ExcelField注解生成excl标题信息 指定appoints-》@ExcelField(name = "描述") name
     *
     * @param tClass
     * @param appoints
     * @return
     */
    public static Map<String, String> getHeaderAliasAppoints(Class<?> tClass, List<String> appoints) {
        Field[] declaredFields = tClass.getDeclaredFields();
        Map<String, String> stringMap = new LinkedHashMap<>();
        for (Field declaredField : declaredFields) {
            declaredField.setAccessible(true);
            ExcelField annotation = declaredField.getAnnotation(ExcelField.class);
            if (annotation != null) {

                String[] values = annotation.values();
                for (String name : values) {
                    if (appoints.contains(name)) {
                        stringMap.put(declaredField.getName(), name);
                    }
                }

            }
        }
        return stringMap;
    }

    /**
     * 读取指定字段
     *
     * @param file
     * @param tClass
     * @param maps
     * @return
     * @throws IOException
     */
    public static List<?> exclRead(MultipartFile file, Class<?> tClass, List<ExcelFieldMap> maps) throws IOException {
        InputStream inputStream = file.getInputStream();
        //创建对象
        ExcelReader reader = ExcelUtil.getReader(inputStream);
        maps.forEach(vo -> reader.addHeaderAlias(vo.getName(), vo.getFieldName()));
        List<?> list = reader.readAll(tClass);
        //关闭
        reader.close();
        return list;
    }

    /**
     * 读取
     *
     * @param file
     * @param tClass
     * @return
     * @throws IOException
     */
    public static List<?> excelRead(MultipartFile file, Integer sheet, String sheetName, Class<?> tClass) throws IOException {
        InputStream inputStream = file.getInputStream();

        //创建对象
        ExcelReader reader = ExcelUtil.getReader(inputStream);
        if (sheet != null) {
            reader.setSheet(sheet);
        }
        if (sheetName != null) {
            reader.setSheet(sheetName);
        }
        if (sheet == null && sheetName == null) {
            reader.setSheet("Sheet1");
        }
        Map<String, String> headerAlias = getHeaderAliasForRead(tClass);
        Set<String> keySet = headerAlias.keySet();
        keySet.stream().forEach(name -> reader.addHeaderAlias(name, headerAlias.get(name)));
        List<?> list = reader.readAll(tClass);
        //关闭
        reader.close();
        return list;
    }

    /**
     * @param inputStream
     * @param tClass
     * @return
     * @throws IOException
     */
    public static List<?> excelRead(InputStream inputStream, Integer sheet, String sheetName, Class<?> tClass) throws IOException {
        //创建对象
        ExcelReader reader = ExcelUtil.getReader(inputStream);
        if (sheet != null) {
            reader.setSheet(sheet);
        }
        if (sheetName != null) {
            reader.setSheet(sheetName);
        }

        if (sheet == null && sheetName == null) {
            reader.setSheet("Sheet1");
        }
        Map<String, String> headerAlias = getHeaderAliasForRead(tClass);
        Set<String> keySet = headerAlias.keySet();
        keySet.stream().forEach(name -> reader.addHeaderAlias(name, headerAlias.get(name)));
        List<?> list = reader.readAll(tClass);
        //关闭
        reader.close();
        return list;
    }


    /**
     * 导出排除指定字段
     *
     * @param response
     * @param excludes
     * @param tClass
     * @param fileName
     * @throws IOException
     */
    public static void excludesExcelOut(HttpServletResponse response, List<String> excludes, Class<?> tClass, String fileName) throws IOException {
        ExcelWriter writer = ExcelUtil.getWriter();
        Map<String, String> headerAlias = ExcelUtils.getHeaderAliasExcludes(tClass, excludes);
        //设置字段别名
        writer.setHeaderAlias(headerAlias);
        //标题
        writer.writeHeadRow(headerAlias.values());

        response.setContentType("application/vnd.ms-excel;charset=utf-8");

        response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8") + ".xlsx");

        ServletOutputStream out;
        out = response.getOutputStream();
        writer.flush(out, true);
    }

    /**
     * 导出指定字段模板
     *
     * @param response
     * @param appoints
     * @param tClass
     * @param fileName
     * @throws IOException
     */
    public static void excelOut(HttpServletResponse response, List<String> appoints, Class<?> tClass, String fileName) throws IOException {
        ExcelWriter writer = ExcelUtil.getWriter();
        Map<String, String> headerAlias = ExcelUtils.getHeaderAliasAppoints(tClass, appoints);
        //设置字段别名
        writer.setHeaderAlias(headerAlias);
        //标题
        writer.writeHeadRow(headerAlias.values());

        response.setContentType("application/vnd.ms-excel;charset=utf-8");

        response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8") + ".xlsx");

        ServletOutputStream out;
        out = response.getOutputStream();
        writer.flush(out, true);
    }

    /**
     * 导出指定字段数据或者模板(list == null or list.size()==0)
     *
     * @param response
     * @param appoints
     * @param list
     * @param tClass
     * @param fileName
     * @throws IOException
     */
    public static void excelOut(HttpServletResponse response, List<String> appoints, List<?> list, Class<?> tClass, String fileName) throws IOException {
        ExcelWriter writer = ExcelUtil.getWriter();
        Map<String, String> headerAlias = ExcelUtils.getHeaderAliasAppoints(tClass, appoints);
        //设置字段别名
        writer.setHeaderAlias(headerAlias);
        //标题
        writer.writeHeadRow(headerAlias.values());


        if (!CollectionUtils.isEmpty(list)) {
            writer.write(list);
        }

        response.setContentType("application/vnd.ms-excel;charset=utf-8");

        response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8") + ".xlsx");

        ServletOutputStream out;
        out = response.getOutputStream();
        writer.flush(out, true);
    }

    public static void excelOut(HttpServletResponse response, List<ExcelFieldMap> maps, List<?> list, String fileName) throws IOException {
        ExcelWriter writer = ExcelUtil.getWriter();
        Map<String, String> headerAlias = ExcelUtils.getHeaderAliasDynamicFieldMap(maps);
        //设置字段别名
        writer.setHeaderAlias(headerAlias);
        //标题
        writer.writeHeadRow(headerAlias.values());

        if (!CollectionUtils.isEmpty(list)) {
            writer.write(list);
        }

        response.setContentType("application/vnd.ms-excel;charset=utf-8");

        response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8") + ".xlsx");

        ServletOutputStream out;
        out = response.getOutputStream();
        writer.flush(out, true);
    }

    // 以下方法属于理论可行未测试过

    /**
     * 封装 通用导出方法
     *
     * @param response
     * @param dataList
     * @param fileName
     */
    public static void universalExcelExport(HttpServletResponse response, List<Map<String, Object>> dataList, String fileName) {
        ExcelWriter writer;
        try {
            //BigExcelWriter 非超级管理员在linux 中运行会报错:没有权限
            writer = ExcelUtil.getBigWriter();
        } catch (Exception e) {
            writer = ExcelUtil.getWriter();
        }
        ServletOutputStream out = null;
        try {
            writer.write(dataList, true);
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");
            fileName = URLEncoder.encode(fileName, "UTF-8");
            response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
            response.setHeader("Content-Disposition", "attachment;filename=" + fileName + ".xlsx");
            out = response.getOutputStream();
            writer.flush(out, true);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            writer.close();
            IoUtil.close(writer);
        }
    }

    /**
     * 通用数据封装
     *
     * @param object
     * @return
     * @throws IllegalAccessException
     */
    public static Map<String, Object> entityToMap(Object object) throws IllegalAccessException {
        Map<String, Object> resMap = new LinkedHashMap<>();
        Class<?> albumClass = object.getClass();
        // 获取 实体类 中所有的字段
        Field[] declaredFields = albumClass.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            // 这里设置为 true 才可以访问到 实体类中的 private 字段
            declaredField.setAccessible(true);
            // 获取字段所对应的注解
            ExcelField annotation = declaredField.getAnnotation(ExcelField.class);
            if (ObjectUtils.isNotEmpty(annotation)) {
                // 有注解 则 获取 注解的值（表头名称）
                String[] values = annotation.values();
                for (String value : values) {
                    resMap.put(value, declaredField.get(object));
                }
            }
        }
        return resMap;
    }

    /**
     * 通用数据封装
     *
     * @param object
     * @param names
     * @return
     * @throws IllegalAccessException
     */
    public static Map<String, Object> entityToMap(Object object, List<String> names) throws IllegalAccessException {
        Map<String, Object> resMap = new LinkedHashMap<>();
        Class<?> albumClass = object.getClass();
        // 获取 实体类 中所有的字段
        Field[] declaredFields = albumClass.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            // 这里设置为 true 才可以访问到 实体类中的 private 字段
            declaredField.setAccessible(true);
            // 获取字段所对应的注解
            ExcelField annotation = declaredField.getAnnotation(ExcelField.class);
            if (ObjectUtils.isNotEmpty(annotation)) {
                // 有注解 则 获取 注解的值（表头名称）
                String[] values = annotation.values();
                for (String value : values) {
                    // 再判断 实体类中是否有这个表头
                    if (names.contains(value)) {
                        resMap.put(value, declaredField.get(object));
                    }
                }

            }
        }
        return resMap;
    }

    /**
     * 通用数据封装
     *
     * @param object
     * @return
     * @throws IllegalAccessException
     */
    public static <T> Map<String, Object> entityToMapV1(T object) throws IllegalAccessException {
        Map<String, Object> resMap = new LinkedHashMap<>();
        Class<?> albumClass = object.getClass();
        // 获取 实体类 中所有的字段
        Field[] declaredFields = albumClass.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            // 这里设置为 true 才可以访问到 实体类中的 private 字段
            declaredField.setAccessible(true);
            // 获取字段所对应的名称和值
            String name = declaredField.getName();
            resMap.put(name, declaredField.get(object));
        }
        return resMap;
    }

    /**
     * 通用数据封装
     *
     * @param object
     * @param names
     * @return
     * @throws IllegalAccessException
     */
    public static <T> Map<String, Object> entityToMapV1(T object, List<String> names) throws IllegalAccessException {
        Map<String, Object> resMap = new LinkedHashMap<>();
        Class<?> albumClass = object.getClass();
        // 获取 实体类 中所有的字段
        Field[] declaredFields = albumClass.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            // 这里设置为 true 才可以访问到 实体类中的 private 字段
            declaredField.setAccessible(true);
            // 获取字段所对应的注解
            ExcelField annotation = declaredField.getAnnotation(ExcelField.class);
            if (ObjectUtils.isNotEmpty(annotation)) {
                // 有注解 则 获取 注解的值（表头名称）
                String[] values = annotation.values();
                for (String value : values) {
                    // 再判断 实体类中是否有这个表头
                    if (names.contains(value)) {
                        resMap.put(value, declaredField.get(object));
                    }
                }
            }
        }
        return resMap;
    }

    /**
     *
     * @param objects
     * @param names
     * @return
     * @param <T>
     */
    public static <T> List<Map<String, Object>> entitysToMaps(List<T> objects, List<String> names) {
        List<Map<String, Object>> mapList = objects.stream().map(object -> {
            try {
                return entityToMapV1(object, names);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }).collect(Collectors.toList());
        return mapList;
    }

    /**
     *
     * @param objects
     * @return
     * @param <T>
     */
    public static <T> List<Map<String, Object>> entitysToMaps(List<T> objects) {
        List<Map<String, Object>> mapList = objects.stream().map(object -> {
            try {
                return entityToMapV1(object);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }).collect(Collectors.toList());
        return mapList;
    }


}
